﻿#region header

//  ***************************************************************************
// *                                LICENSE
//  ***************************************************************************
//  * "Haplous" .NET Simple 2D Game Framework
//  * 
//  * Copyright © Jonathan Evans 2014-2014
//  * 
//  * This program is free software; you can redistribute it and/or
//  * modify it under the terms of the GNU General Public License
//  * as published by the Free Software Foundation; either version 2
//  * of the License, or (at your option) any later version.
//  * 
//  * This program is distributed in the hope that it will be useful,
//  * but WITHOUT ANY WARRANTY; without even the implied warranty of
//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  * GNU General Public License for more details.
//  * 
//  * You should have received a copy of the GNU General Public License
//  * along with this program; if not, write to the Free Software
//  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//  * 
//  ***************************************************************************
//  * 
//  * Contact: opencollar@googlemail.com
//  * 
//  * Website: http://haplous.codeplex.com/
//  *
//  * NuGet:   http://www.nuget.org/packages/haplous/
//  *
//  * File:    Haplous/Ghosthunt/Ghosthunter.cs
//  *
//  ***************************************************************************

#endregion

using System;
using System.ComponentModel;

using Ghosthunt.Resources;

using Haplous;
using Haplous.Core;

namespace Ghosthunt
{
    /// <summary>
    ///     The Actor used to represent the main character in the game.
    /// </summary>
    internal class Ghosthunter : Actor
    {
        /// <summary>
        ///     A lock used to control access to the <see cref="_reseting" /> flag and functionality.
        /// </summary>
        private readonly object _resetingLock = new object();

        /// <summary>
        ///     A flag indicating that this Actor is dying.
        /// </summary>
        private bool _dead;

        /// <summary>
        ///     The number of times an image from the death sequence has been shown.
        /// </summary>
        private int _deadIterations;

        /// <summary>
        ///     A flag indicating that we are in the process of reseting this character.
        /// </summary>
        private bool _reseting;

        /// <summary>
        ///     Initializes a new instance of the <see cref="ElementBase" /> class.
        /// </summary>
        /// <param name="game">
        ///     The game to which the class belongs.  The <paramref name="game" /> must not be
        ///     <see langword="null" />.
        /// </param>
        public Ghosthunter(Game game) : base(game, "Ghosthunter")
        {
            Game.ActiveMode = Game.Modes["Normal"];

            // We create a picture sequence for each direction in which we travel.
            AddSequence(DirectionKind.Up);
            AddSequence(DirectionKind.Right);
            AddSequence(DirectionKind.Down);
            AddSequence(DirectionKind.Left);
            AddWaitSequence();
            AddDeathSequence();

            // And set it to be our picture
            PictureSource.Source = Game.PictureSequences["GhosthunterNone"];
            Visualization.Visible = true;
            Visualization.Origin = OriginKind.Center;
            Visualization.Scaling = PictureScalingKind.Stretch;
            Visualization.Tiling = TileKind.None;

            Size.Set(64, 64);

            Director = new GhosthunterDirector(Game, "NormalGhosthunterController", this, Game.Cues["Animation"], 15);
            Director.CurrentDirection.PropertyChanged += DirectionChanged;

            // Now make sure that the appearance and behaviour when in "Ghost Hunting" mode matches normal mode
            CopyModeProperties(Game.Modes["Normal"], Game.Modes["GhostHunt"], new[] {"Director"});

            // And make the controller different (we can move faster and eat ghosts!)
            Game.ActiveMode = Game.Modes["GhostHunt"];
            Director = new GhosthunterDirector(Game, "GhostHuntGhosthunterController", this, Game.Cues["Animation"], 17);
            Director.CurrentDirection.PropertyChanged += DirectionChanged;
        }

        /// <summary>
        ///     Gets a unique name that can be used to identify this object.
        /// </summary>
        /// <value>The unique name of this item.</value>
        /// <remarks>The name must never be <see langword="null" />, empty or whitespace.</remarks>
        public override string Name
        {
            get { return "Ghosthunter"; }
        }

        /// <summary>
        ///     Adds the death sequence.
        /// </summary>
        private void AddDeathSequence()
        {
            // Get the static pictures that will be used for the death animation
            var pic1 = Game.AddPicture("Ghosthunter-Death-01", ResourcesHelper.GetBitmap("Ghosthunter_Death01"));
            var pic2 = Game.AddPicture("Ghosthunter-Death-02", ResourcesHelper.GetBitmap("Ghosthunter_Death02"));
            var pic3 = Game.AddPicture("Ghosthunter-Death-03", ResourcesHelper.GetBitmap("Ghosthunter_Death03"));

            // Now create the animated picture that will be used in the game for the static animation.
            var timerSync = Game.Cues["Animation"];
            var animation = new SimplePictureSequence(Game, "GhosthunterDeath", timerSync, IterationKind.LoopLeftToRight);
            animation.AddPictures(pic1, pic2, pic3);
            Game.PictureSequences.Add(animation);
        }

        /// <summary>
        ///     Adds the wait sequence.
        /// </summary>
        private void AddWaitSequence()
        {
// Get the static pictures that will be used for the static animation
            var pic1 = Game.AddPicture("Ghosthunter-Wait-01", ResourcesHelper.GetBitmap("Ghosthunter_Wait01"));
            var pic2 = Game.AddPicture("Ghosthunter-Wait-02", ResourcesHelper.GetBitmap("Ghosthunter_Wait02"));
            var pic3 = Game.AddPicture("Ghosthunter-Wait-03", ResourcesHelper.GetBitmap("Ghosthunter_Wait03"));

            // Now create the animated picture that will be used in the game for the static animation.
            var timerSync = Game.Cues["Animation"];
            var animation = new SimplePictureSequence(Game, GetSequenceName(DirectionKind.None), timerSync, IterationKind.LoopLeftToRight);
            animation.AddPictures(pic1, pic2, pic3);
            Game.PictureSequences.Add(animation);
        }

        /// <summary>
        ///     Called when the active mode has changed.
        /// </summary>
        /// <param name="previousMode">The previous mode.</param>
        /// <param name="newMode">The new mode.</param>
        protected override void OnModeChanged(Mode previousMode, Mode newMode)
        {
            if(previousMode == null)
                return;

            CopyModeProperties(previousMode, newMode, new[] {"Director", "Opacity"});
        }

        /// <summary>
        ///     Occurs when an item that is attempting to move is found to be in collision with this item.
        /// </summary>
        /// <param name="item">The item that is attempting to move.</param>
        /// <param name="oldPosition">The starting position of the item that has changed.</param>
        /// <param name="oldSize">The original size of the item that has changed.</param>
        /// <param name="newPosition">The proposed new position of the item that has changed.</param>
        /// <param name="newSize">The proposed new size of the item that has changed.</param>
        /// <returns>The outcome of the collision.</returns>
        /// <remarks>
        ///     The intention of this method is to allow the receiver to make a decision about, but not yet react, to an impact.
        ///     The reaction should occur when the <see cref="SolidGraphicBlock.OnCollision" /> method is called.
        /// </remarks>
        public override OutcomeKind OnColliding(SolidGraphicBlock item, PositionInt32 oldPosition, SizeInt32 oldSize, PositionInt32 newPosition, SizeInt32 newSize)
        {
            // If it is a ghost then we will react!
            if(item is Ghost)
            {
                // We can just plough over ghosts in ghost hunt mode.
                if(((Ghosthunt)Game).InGhostHuntMode)
                    return OutcomeKind.Permitted;

                return OutcomeKind.Reactive;
            }

            // Otherwise just bounce off.
            return OutcomeKind.Bounce;
        }

        /// <summary>
        ///     Called when another element has moved and a collision has occurred.
        /// </summary>
        /// <param name="item">The item that has moved.</param>
        /// <param name="anticipatedOutcome">
        ///     The anticipated outcome from the previous call to
        ///     <see cref="SolidGraphicBlock.OnColliding" />.
        /// </param>
        /// <remarks>This method is called to allow the recipient to react to the collision.</remarks>
        public override void OnCollision(SolidGraphicBlock item, OutcomeKind anticipatedOutcome)
        {
            // What have we collided with?
            if(item is Ghost)
            {
                // We can just plough over ghosts in ghost hunt mode.
                if(((Ghosthunt)Game).InGhostHuntMode)
                    return;

                // A ghost?  Oh dear!

                // Prevent the Ghosthunter from moving any more.
                var director = (KeyboardDirector)Director;
                if(director != null) // We have to check because if this occurs at the exact moment we change between modes it can be null.
                    director.Enabled = false;

                // If we haven't done so already the picture to start showing the death sequence
                if(!_dead)
                {
                    // We might be called several times because the ghost will continue to try to move.

                    _dead = true;
                    var timerSync = Game.Cues["Animation"];
                    timerSync.Tick += AnimationTick;
                    PictureSource.Source = Game.PictureSequences["GhosthunterDeath"];
                    _deadIterations = 1;
                }
            }
        }

        /// <summary>
        ///     Called when the animation timer ticks.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void AnimationTick(object sender, EventArgs e)
        {
            // If we have already started to reset then go no further.
            if(_reseting)
                return;

            lock(_resetingLock)
            {
                // Keep count of which frame in the animation we are on - we want to finish when we have been through all of them
                ++_deadIterations;

                // Have we displayed each frame?
                var sequence = Game.PictureSequences["GhosthunterDeath"];
                if(_deadIterations <= sequence.Count)
                    return; // No, so leave now and let it carry on.

                // Yes?  We have finished the picture sequence, so now let's start a new round.
                _reseting = true;
                try
                {
                    var timerSync = Game.Cues["Animation"];
                    timerSync.Tick -= AnimationTick; // We don't want to process this tick event any more, so unsubscribe

                    // Now reset the character back to normal
                    PictureSource.Source = Game.PictureSequences["GhosthunterNone"];
                    _dead = false;
                    _deadIterations = 0;
                    ((KeyboardDirector)Director).Enabled = true;

                    // Finally, tell the main game to start a new round.
                    ((Ghosthunt)Game).StartRound();
                }
                finally
                {
                    // Use a try..catch..finally block to ensure that this gets set back to false, even if we have an error
                    _reseting = false;
                }
            }
        }

        /// <summary>
        ///     Called when the direction changes.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="PropertyChangedEventArgs" /> instance containing the event data.</param>
        private void DirectionChanged(object sender, PropertyChangedEventArgs e)
        {
            try
            {
                if(_dead)
                {
                    // If we're in the death sequence make sure we don't change
                    PictureSource.Source = Game.PictureSequences["GhosthunterDeath"];
                    return;
                }

                // Update the animation to reflect the direction in which we are traveling.
                var name = GetSequenceName(Director.CurrentDirection.Value);
                PictureSource.Source = Game.PictureSequences[name];
            }
            catch(Exception ex)
            {
                ErrorManager.OnUnhandledException(ex);
            }
        }

        /// <summary>
        ///     Adds the picture sequence for the direction specified.
        /// </summary>
        /// <param name="direction">The direction for which the sequence will be used..</param>
        private void AddSequence(DirectionKind direction)
        {
            // Get the static pictures that will be used for the animation
            var pic1 = Game.AddPicture("Ghosthunter-" + direction + "-01", ResourcesHelper.GetBitmap("Ghosthunter_" + direction + "01"));
            var pic2 = Game.AddPicture("Ghosthunter-" + direction + "02", ResourcesHelper.GetBitmap("Ghosthunter_" + direction + "02"));
            var pic3 = Game.AddPicture("Ghosthunter-" + direction + "03", ResourcesHelper.GetBitmap("Ghosthunter_" + direction + "03"));

            // Now create the animated picture that will be used in the Game.
            var timerSync = Game.Cues["Animation"];
            var animation = new SimplePictureSequence(Game, GetSequenceName(direction), timerSync, IterationKind.LoopLeftToRight);
            animation.AddPictures(pic1, pic2, pic3);
            Game.PictureSequences.Add(animation);
        }

        /// <summary>
        ///     Gets the name for the picture sequence for the direction given.
        /// </summary>
        /// <param name="direction">The direction for the sequence.</param>
        /// <returns>A string used for naming a sequence.</returns>
        private static string GetSequenceName(DirectionKind direction)
        {
            return "Ghosthunter" + direction;
        }
    }
}